package main;
import java.awt.Graphics2D;
import java.util.ArrayList;

public class Grid {
	
	static final int ROW = 30, COLUMN = 50, LENGTH = 20;
	static Location grid[][] = new Location[ROW][COLUMN];
	
	public static Location startLoc;
	public static Location endLoc;
	private static double obstacleDensity = 0.5;
	
	public Grid()
	{
		createGrid();
	}
	public void draw(Graphics2D g)
	{
		for(int row = 0; row < ROW; row++)
		{
			for(int column = 0; column < COLUMN; column++)
				grid[row][column].draw(g);
		}
	}
	public static ArrayList<Location> getAdjacentOpenLocations(Location loc, int range)
	{
		int start_row = loc.row() - range, start_column = loc.col() - range;
		ArrayList<Location> valid_locations = new ArrayList<Location>();
		for(int current_row = start_row; current_row < start_row+(3+(2*(range - 1))); current_row++)
		{
			for(int current_column = start_column; current_column < start_column+(3+(2*(range-1))); current_column++)
			{
				if (current_row >= 0 && current_row < ROW && current_column>=0 && current_column < COLUMN) 
				{
					if(!(current_row == loc.row() && current_column == loc.col()))
					{
						if(!grid[current_row][current_column].isBadLocation())
						{
							valid_locations.add(grid[current_row][current_column]);
						}
					}
				}
			}
		}
		return valid_locations;
	}
	public static ArrayList<Location> getAdjacentLocations(Location loc, int range)
	{
		int start_row = loc.row() - range, start_column = loc.col() - range;
		ArrayList<Location> valid_locations = new ArrayList<Location>();
		for(int current_row = start_row; current_row < start_row+(3+(2*(range - 1))); current_row++)
		{
			for(int current_column = start_column; current_column < start_column+(3+(2*(range-1))); current_column++)
			{
				if (current_row >= 0 && current_row < ROW && current_column>=0 && current_column < COLUMN) 
				{
					if(!(current_row == loc.row() && current_column == loc.col()))
						valid_locations.add(grid[current_row][current_column]);
				}
			}
		}
		return valid_locations;
	}
	public void createGrid()
	{
		for (int row = 0; row < ROW; row++)
		{
			for(int column = 0; column < COLUMN; column++)
				grid[row][column] = new Location(row, column, Math.random() <= obstacleDensity);
		}
	}
	public void createFullyNavigableGrid()
	{
		createGrid();//creates a grid
		randomlySelectEndPoints(10);//this method will create a grid with a path at maximum range
		clearGrid(true);//since the above method also sets end points which we don't want, we call this method.
	}
	public static boolean allUnreachable()//returns true if all locations on the grid are unreachable
	{
		for (int row = 0; row < ROW; row++)
		{
			for(int column = 0; column < COLUMN; column++)
			{
				if(grid[row][column].isReachable())
					return false;
			}
		}
		return true;
	}
	public void clearGrid(boolean removeEndPoint)
	{
		for (int row = 0; row < ROW; row++)
		{
			for(int column = 0; column < COLUMN; column++) {
				grid[row][column].reset(removeEndPoint);
			}
		}
	}
	public boolean setEndPoint(int row, int column, int click_count)//returns true if the end point isn't set
	{
		boolean notSet = false;
		if (click_count <= 1) {
			if (click_count == 0) notSet = grid[row][column].setStartPoint();//sets to true if not set
			else notSet = grid[row][column].setEndPoint();//sets to true if not set
		}
		else
			clearGrid(true);
		return notSet;
	}
	public int[] randomlySelectEndPoints(int rangeNumber)
	{
		int randomStartRow, randomEndRow, randomStartCol, randomEndCol, currentRange;
		int numTimesTried = 0;
		boolean endPointSelected;
		do
		{
			endPointSelected = true;
			randomStartRow = (int)(Math.random()*ROW);
			randomEndRow = (int)(Math.random()* ROW);
		    randomStartCol = (int)(Math.random()*COLUMN);
		    randomEndCol = (int)(Math.random()* COLUMN);
		    currentRange = Math.max(Math.abs(randomStartRow - randomEndRow), Math.abs(randomStartCol - randomEndCol));
		    
		    if (currentRange <= rangeNumber*5 && currentRange > 5*(rangeNumber-1))
		    {
		    	if (isValidGridSpace(randomStartRow, randomStartCol) && isValidGridSpace(randomEndRow, randomEndCol))
		    	{
		    		if (Astar(grid[randomStartRow][randomStartCol], grid[randomEndRow][randomEndCol]) != null)
		    		{
		    			setEndPoint(randomStartRow, randomStartCol, 0);
		    			setEndPoint(randomEndRow, randomEndCol, 1);
		    		}
		    		else
		    			endPointSelected = false;
		    	}
		    	else
		    		endPointSelected = false;
		    }
		    else
		    	endPointSelected = false;
		    numTimesTried++;
		    if (numTimesTried >= 2000 && !endPointSelected && rangeNumber == 10)//only purpose of this statement is to make
		    {                                                                   //sure that a path exists at the maximum range
		    	createGrid();
		    	numTimesTried = 0;
		    }
		}
		while (!endPointSelected);
		int[] couplet = new int[2];
		couplet[0] = Astar(startLoc, endLoc).size()-1;
		couplet[1] = currentRange;
		return couplet;
	}
	//returns true if the grid space is an open space or false if it isn't
	public boolean isValidGridSpace(int row, int col)
	{
		return !grid[row][col].isBadLocation();
	}
	public ArrayList<Location> Astar(Location start,Location goal)
	{
		ArrayList<Location> closedList = new ArrayList<Location>(),
		openList = new ArrayList<Location>();
		openList.add(start);
		start.setParent(null);//sets parent to null and doesn't change g_value (which is initialized to 0)
		start.setHeuristicValue(goal);//calculates h_value based on the locations distance from the goal and also sets the f_value
		
		while(!openList.isEmpty())
		{
			Location current = getLocationWithLowestFScore(openList);
			if(current == goal)
				return getFinalPath(goal);
			openList.remove(current);
			closedList.add(current);
			for(Location childLoc: current.getAdjacentLocations())
			{
				if(closedList.contains(childLoc))
					continue;
				int tenative_g_score = current.getGValue() + 10;
				boolean tenative_is_better;
				if(!openList.contains(childLoc))
				{
					openList.add(childLoc);
					tenative_is_better = true;
				}
				else if (tenative_g_score < childLoc.getGValue())
					tenative_is_better = true;
				else
					tenative_is_better = false;
				if(tenative_is_better)
				{
					childLoc.setParent(current);
					childLoc.setGValue(tenative_g_score);
					childLoc.setHeuristicValue(goal);
				}
			}
		}
		clearGrid(false); //this line was added to reset the locations h, g, f and parent values
		return null; //search has failed to yield any results
	}
	public Location getLocationWithLowestFScore(ArrayList<Location> list)
	{
		Location lowestFScore = list.get(0);
		for(int index = 1; index < list.size(); index++)
			lowestFScore = list.get(index).getFValue() < lowestFScore.getFValue() ? list.get(index) : lowestFScore;
		return lowestFScore;
	}
	public ArrayList<Location> getFinalPath(Location endLoc)
	{
		ArrayList<Location> finalPath = new ArrayList<Location>();
		finalPath.add(endLoc);
		while(endLoc.getParent() != null)
		{
			endLoc = endLoc.getParent();
			finalPath.add(endLoc);
		}
		clearGrid(false);//this line was added to reset the locations h, g, f and parent values
		return finalPath;
	}
	public Location getStartLocation()
	{
		return startLoc;
	}
	public static boolean isEndPoint(Location loc)
	{
		return loc == endLoc;
	}
	public void changeObjectDensity(double densityNumber)
	{
		obstacleDensity = densityNumber;
		createFullyNavigableGrid();//recreates the grid based on the new obstacleDensity
	}
	public static double getObstacleDensity()
	{
		return obstacleDensity;
	}
}
